﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Net;
using System.IO;
using System.Xml;
using System.Security;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using Office = Microsoft.Office.Core;
using ThreeByte.Security;
//using ThreeByte.ActiveDeck.Network;
using ThreeByte.ActiveDeck.Service;
using ThreeByte.ActiveDeck.Data;
using System.ComponentModel;
using log4net;
using ThreeByte.ActiveDeck.AddIn.Config;
using System.Windows.Media;
using System.Collections.ObjectModel;
using ThreeByte.ActiveDeck.AddIn.Controls.Wizard.Themes;

namespace ThreeByte.ActiveDeck.Config
{
    public class CoreConfig : INotifyPropertyChanged
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(CoreConfig));

        private static readonly XNamespace ns = ServiceConfiguration.NAMESPACE;

        private Dictionary<int, SlideConfig> _config = new Dictionary<int, SlideConfig>();
        

        public CoreConfig(PowerPoint.Presentation pres) {
            Presentation = pres;
            PresentationPath = pres.Path;
            Graphics = new GraphicStore();
            PresentationGroups = new ObservableCollection<PresentationGroup>();
            PresenterLogin = new NetworkCredential();
            Environment = new EnvironmentContainer();
            //UserAccess = DataUtil.DEFAULT_USERACCESS;
            UserAccess = UserAccess.Anonymous;  //During testing make the defaults easier to get started with
        }

        public PowerPoint.Presentation Presentation {
            get;
            private set;
        }
        private string PresentationPath;

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public SlideConfig this[int i] {
            get {
                return _config[i];
            }
            set {
                _config[i] = value;
                _config[i].Parent = this;
                MarkAsDirty(); //Mark as dirty whenever adding a new slide (modifying the slide set)
            }
        }

        public IEnumerable<int> SlideIDs {
            get {
                return _config.Keys.OrderBy(i => i);
            }
        }

        public EnvironmentContainer Environment {
            get;
            private set;
        }

        private string _id;
        public string ID {
            get {
                return _id;
            }
            set {
                _id = value;
                NotifyPropertyChanged("ID");
                NotifyPropertyChanged("HasPresentationID");
            }
        }

        private string _name;
        public string Name {
            get {
                return _name;
            }
            set {
                _name = value;
                NotifyPropertyChanged("Name");
                NotifyPropertyChanged("HasPresentationID");
            }
        }

        private WizardTheme _theme;
        public WizardTheme Theme {
            get {
                return (_theme ?? WizardTheme.Default);
            }
            set {
                _theme = value;
                if(_theme != null) {
                    //Make sure the backgroud graphic is available for assignment in the config graphics
                    Graphics.AddIfNotPresent(_theme.Background);
                }
            }
        }

        private bool _allowExportToPDF = true; //By default;
        public bool AllowExportToPDF {
            get {
                return _allowExportToPDF;
            }
            set {
                _allowExportToPDF = value;
                NotifyPropertyChanged("AllowExportToPDF");
            }
        }

        public UserAccess UserAccess {
            get;
            set;
        }

        public GraphicResource Logo {
            get;
            set;
        }

        public GraphicResource HomePage {
            get;
            set;
        }

        public GraphicResource EndPage {
            get;
            set;
        }

        public Color DefaultButtonOverlayColor {
            get;
            set;
        }

        public KioskConfiguration KioskConfig {
            get;
            set;
        }

        public string LocalWebserviceEndpoint {
            get;
            set;
        }

        public GraphicStore Graphics {
            get;
            private set;
        }

        public bool StoreGraphicsSeparately {
            get;
            set;
        }

        public NetworkCredential PresenterLogin {
            get;
            private set;
        }

        public ObservableCollection<PresentationGroup> PresentationGroups {
            get;
            set;
        }

        public void RemoveId(int id) {
            _config.Remove(id);
        }

        public bool ContainsId(int id) {
            return _config.ContainsKey(id);
        }

        public bool ContainsPageId(int pageId) {
            return _config.Any(k => k.Value.PageIDs.Contains(pageId));
        }

        public int GetSectionForPageId(int pageId) {
            foreach(int k in _config.Keys) {
                if(_config[k].PageIDs.Contains(pageId)) {
                    return k;
                }
            }
            //Else, page not found
            return 0;
        }

        public bool HasPresentationID {
            get {
                return (!(string.IsNullOrWhiteSpace(ID))
                        && !(string.IsNullOrWhiteSpace(Name)));
            }
        }

        public bool HasPresenterLogin {
            get {
                return (!(string.IsNullOrWhiteSpace(PresenterLogin.UserName))
                        && !(string.IsNullOrWhiteSpace(PresenterLogin.Password)));
            }
        }

        //TODO: Find out when it is appropriate to use this
        public void MarkAsDirty() {
            log.Debug("Mark as Dirty");
            Presentation.Saved = Office.MsoTriState.msoFalse;
        }

        //Go through and purge any configuration or resources associated with slies that no longer exist.
        public void PruneUnusedSlides() {

            HashSet<int> existingSlideIds = new HashSet<int>();
            foreach(PowerPoint.Slide slide in Presentation.Slides) {
                existingSlideIds.Add(slide.SlideID);
            }

            foreach(int currentId in _config.Keys.ToList().OrderBy(p => p)) {
                if(!(existingSlideIds.Contains(currentId))) {
                    //Delete the configured slide from the memeory model
                    log.InfoFormat("Removing Unused Slide: Index {0}", currentId);
                    _config.Remove(currentId);   
                }
            }
        }

        public IEnumerable<SlideConfig> Slides {
            get {
                foreach(int i in SlideIDs) {
                    yield return this[i];
                }
            }
        }

        public string ToXML() {

            XDocument doc = new XDocument();
            XElement root = new XElement(ns + "Config", new XAttribute("ID", ID ?? string.Empty),
                                                        new XAttribute("Name", Name ?? string.Empty));
            doc.Add(root);

            //Store Authentication Info
            root.Add(AuthorizationUtil.ToAuthenticationXML(PresenterLogin));

            root.Add(Environment.ToXML());

            //Store UserAccess
            root.Add(new XElement(ns + "UserAccess", new XAttribute("Value", UserAccess)));

            root.Add(new XElement(ns + "ExportToPDF", new XAttribute("Allow", AllowExportToPDF)));

            if(!(string.IsNullOrWhiteSpace(LocalWebserviceEndpoint))) {
                root.Add(new XElement(ns + "WebService", new XAttribute("Endpoint", LocalWebserviceEndpoint)));
            }

            if(KioskConfig != null) {
                root.Add(KioskConfig.ToXml());
            }

            //Add Graphic Resources
            XElement resourceNode = new XElement(ns + "Resources");
            if (Logo != null) {
                resourceNode.Add(Logo.GetXmlHeader());
                XElement logoNode = new XElement(ns + "Logo", new XAttribute("ResourceID", Logo.ID));
                root.Add(logoNode);
            }

            if(HomePage != null) {
                resourceNode.Add(HomePage.GetXmlHeader());
                XElement homePageNode = new XElement(ns + "HomePage", new XAttribute("ResourceID", HomePage.ID));
                root.Add(homePageNode);
            }

            if(EndPage != null) {
                resourceNode.Add(EndPage.GetXmlHeader());
                XElement endPageNode = new XElement(ns + "EndPage", new XAttribute("ResourceID", EndPage.ID));
                root.Add(endPageNode);
            }

            if(DefaultButtonOverlayColor != null) {
                XElement colorNode = new XElement(ns + "DefaultButtonColor", new XAttribute("Color", DefaultButtonOverlayColor.ToString()));
                root.Add(colorNode);
            }

            foreach(GraphicResource g in Graphics) {
                resourceNode.Add(g.GetXmlHeader());
            }
            root.Add(resourceNode);

            if (PresentationGroups.Count > 0) {
                XElement presentationGroupNode = new XElement(ns + "PresentationGroups");
                foreach (PresentationGroup pg in PresentationGroups) {
                    presentationGroupNode.Add(pg.ToXml());
                }
                root.Add(presentationGroupNode);
            }

            int slideCount = _config.Keys.Count;
            //if(slideCount == 0) {
            //    return null;  //No config to save;
            //}
            root.Add(new XAttribute("SlideCount", slideCount));

            foreach (int i in _config.Keys.OrderBy(i => i)) {
                root.Add(this[i].ToXml());
            }
            
            return doc.ToString();
        }


        public static CoreConfig FromXML(PowerPoint.Presentation pres, XElement node, Dictionary<Guid, GraphicResource> resources) {

            //Preserve saved state while loading configuration
            Microsoft.Office.Core.MsoTriState saved = pres.Saved;

            CoreConfig config = new CoreConfig(pres);

            config.ID = node.Attribute("ID").Value;
            config.Name = node.Attribute("Name").Value;

            //Reload Authentication Info
            XElement authNode = node.Element(ns + "Authentication");
            if(authNode != null) {
                config.PresenterLogin = AuthorizationUtil.FromAuthenticationXML(authNode);
            }

            config.Environment = EnvironmentContainer.FromXML(node.Element(ns + "Environment"));

            XElement exportNode = node.Element(ns + "ExportToPDF");
            if(exportNode != null) {
                config.AllowExportToPDF = bool.Parse(exportNode.Attribute("Allow").Value);
            }

            XElement localWebserviceEndpointNode = node.Element(ns + "WebService");
            if(localWebserviceEndpointNode != null) {
                config.LocalWebserviceEndpoint = localWebserviceEndpointNode.Attribute("Endpoint").Value;
            }

            //Retrieve UserAccess Element
            XElement userAccessNode = node.Element(ns + "UserAccess");
            if(userAccessNode != null) {
                config.UserAccess = DataUtil.StringToUserAccess(userAccessNode.Attribute("Value").Value);
            }

            XElement kioskNode = node.Element(KioskConfiguration.XML_NAME);
            if(kioskNode != null) {
                config.KioskConfig = KioskConfiguration.FromXml(kioskNode);
            }

            foreach(XElement sc in node.Elements(ns + "SlideConfig")) {
                SlideConfig newSlideConfig = SlideConfig.FromXml(sc, resources);
                config[newSlideConfig.SlideId] = newSlideConfig;
            }

            HashSet<Guid> specialGraphics = new HashSet<Guid>();
            XElement logoNode = node.Element(ns + "Logo");
            if(logoNode != null) {
                Guid resID = Guid.Parse(logoNode.Attribute("ResourceID").Value);
                config.Logo = resources[resID];
                //Remove the logo from the resources otherwise
                resources.Remove(resID);
                specialGraphics.Add(resID);
            }

            XElement homePageNode = node.Element(ns + "HomePage");
            if(homePageNode != null) {
                Guid resID = Guid.Parse(homePageNode.Attribute("ResourceID").Value);
                config.HomePage = resources[resID];
                resources.Remove(resID);
                specialGraphics.Add(resID);
            }

            XElement endPageNode = node.Element(ns + "EndPage");
            if(endPageNode != null) {
                Guid resID = Guid.Parse(endPageNode.Attribute("ResourceID").Value);
                config.EndPage = resources[resID];
                resources.Remove(resID);
                specialGraphics.Add(resID);
            }

            XElement defaultButtonColorNode = node.Element(ns + "DefaultButtonColor");
            if(defaultButtonColorNode != null) {
                Color defaultColor = (Color)ColorConverter.ConvertFromString(defaultButtonColorNode.Attribute("Color").Value);
                config.DefaultButtonOverlayColor = defaultColor;
            }

            XElement resourceNode = node.Element(ns + "Resources");
            if(resourceNode != null) {
                foreach(XElement g in resourceNode.Elements(ns + "Graphic")) {
                    Guid resID = Guid.Parse(g.Attribute("ID").Value);
                    if(!(specialGraphics.Contains(resID))
                        && resources.ContainsKey(resID)) {
                        config.Graphics.AddIfNotPresent(resources[resID]);
                    }
                }
            }

            XElement presentationGroupsNode = node.Element(ns + "PresentationGroups");
            if (presentationGroupsNode != null) {
                foreach (XElement pg in presentationGroupsNode.Elements()) {
                    PresentationGroup presGroup = PresentationGroup.FromXml(pg);
                    config.PresentationGroups.Add(presGroup);
                }
            }

            //Restore saved state while loading configuration
            pres.Saved = saved;
            return config;
        }



        public void PruneUnusedGraphics() {

            HashSet<Guid> backgroundGraphicsInUse = new HashSet<Guid>();

            //Get the list of all graphic IDs that are referenced as backgrounds
            foreach(int id in SlideIDs) {
                foreach(PageConfig page in this[id].Pages) {
                    if(page.Background != null) {
                        backgroundGraphicsInUse.Add(page.Background.ID);
                    }
                }
            }

            HashSet<Guid> graphicsToRemove = new HashSet<Guid>(this.Graphics.Select(p => p.ID).ToList());

            foreach(Guid g in backgroundGraphicsInUse) {
                graphicsToRemove.Remove(g);
            }

            int graphicsPruned = 0;
            foreach(Guid g in graphicsToRemove) {
                GraphicResource res = Graphics.Where(p => p.ID == g).FirstOrDefault();
                if(res != null) {
                    graphicsPruned++;
                    Graphics.Remove(res);
                }
            }

            log.InfoFormat("Removed {0} unused graphic backgrounds.", graphicsPruned);
        }

        public string GetPresentationPath() {
            return PresentationPath;
        }
    }
}
